﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using P3SS.Networking;
using System.Xml.Serialization;
using System.Text.RegularExpressions;
using P3SS.FailedMail;

namespace P3SS.Engine
{
	/// <summary>
	/// Submits mail to a SMTP server
	/// </summary>
	public class SmtpEngine : AbstractEngine
	{
		#region Constructor
		/// <summary>
		/// Default Constructor
		/// </summary>
		public SmtpEngine()
		{
			protocol = new SmtpProtocol();
			EmailAddress = "";
			cancellationPending = false;
			DomainFilter = "";
			ToFilter = EmailToFilter.SpecificAddress;
			Username = "";
			Password = "";
		}

		/// <summary>
		/// Copy Constructor
		/// </summary>
		/// <param name="inSmtpEngine">SmtpEngine object to deep copy</param>
		public SmtpEngine(SmtpEngine inSmtpEngine)
		{
			protocol = new SmtpProtocol(inSmtpEngine.Protocol);
			EmailAddress = inSmtpEngine.EmailAddress;
			cancellationPending = false;
			DomainFilter = inSmtpEngine.DomainFilter;
			ToFilter = inSmtpEngine.ToFilter;
			Username = "";
			Password = "";
		}
		#endregion
		#region Properties
		/// <summary>
		/// Domain name to filter by
		/// </summary>
		public string DomainFilter { get; set; }

		/// <summary>
		/// What to send the emails to
		/// </summary>
		public EmailToFilter ToFilter { get; set; }

		/// <summary>
		/// The underlying protocol that this engine will use
		/// </summary>
		[XmlIgnore]
		public SmtpProtocol Protocol
		{
			get
			{ return (SmtpProtocol)protocol; }
			set
			{ protocol = value; }
		}

		/// <summary>
		/// Email address to submit all mail to
		/// </summary>
		public string EmailAddress { get; set; }

		#endregion
		#region Public Methods
		/// <summary>
		/// Submits mail items to SMTP server
		/// </summary>
		/// <param name="inMail">Mail items to submit</param>
		/// <returns>Failed mail</returns>
		public List<FailedMailItem> SubmitAllMail(List<string> inMail)
		{
			List<FailedMailItem> FailedMessages = new List<FailedMailItem>();
			PassMessage("Beginning mail submission", null, MessageType.Information);
			bool hasPassedCapabilities = false;
			string myName = base.ToString();
			foreach (string thisMessage in inMail)
			{
				List<string> toAddresses = GetToEmailAddresses(thisMessage);

				if (cancellationPending)
				{
					foreach (string thisAddress in toAddresses)
					{
						FailedMessages.Add(new FailedMailItem() { MailItem = thisMessage, FailureReason = "Service was stopped", EmailAddress = thisAddress, SMTPServer = myName });
					}
				}
				else
				{
					foreach (string thisAddress in toAddresses)
					{
						try
						{
							Protocol.SubmitMail(thisAddress, thisMessage);
						}
						catch (Exception ex)
						{
							FailedMessages.Add(new FailedMailItem() { MailItem = thisMessage, FailureReason = ex.ToString(), EmailAddress = thisAddress, SMTPServer = myName });
							PassMessage(ex.Message, ex.ToString(), MessageType.Error);
						}
					}
					if (!hasPassedCapabilities)
					{
						PassDebugMessage("Server reported the following capabilities:\r\n\r\n" + Protocol.ServerCapabilities);
						hasPassedCapabilities = true;
					}
				}
			}
			PassMessage("Mail submission ok. There are " + FailedMessages.Count + " failed messages.", null, MessageType.Information);

			return FailedMessages;
		}

		/// <summary>
		/// Unsafely attempts to resend a failed mail item
		/// </summary>
		/// <param name="inMail">Failed mail item to resend</param>
		public void ResendFailedMail(FailedMailItem inMail)
		{
			try
			{
				Protocol.SubmitMail(inMail.EmailAddress, inMail.MailItem);
			}
			catch (Exception ex)
			{
				PassMessage(ex.Message, ex.ToString(), MessageType.Error);
				throw ex;
			}
		}

		/// <summary>
		/// Human readable string representing this object
		/// </summary>
		/// <returns>Human readable string specifying the emailAddress, server and port</returns>
		public override string ToString()
		{
			string outString = "";

			switch (ToFilter)
			{
				case EmailToFilter.SpecificAddress:
					outString = EmailAddress;
					break;
				case EmailToFilter.FilterDomain:
					outString = "Filter:*" + DomainFilter;
					break;
				case EmailToFilter.AllToHeaders:
					outString = "To Headers";
					break;
			}

			return outString + "@@" + base.ToString();
		}

		/// <summary>
		/// Short, human readable representation of this object
		/// </summary>
		/// <returns>Human readable string specifying the server and port</returns>
		public string ToShortString()
		{
			return base.ToString();
		}

		/// <summary>
		/// Compares this SmtpEngine to another SmtpEngine. Note that FailedMessages are not compared
		/// </summary>
		/// <param name="obj">SmtpEngine to compare</param>
		/// <returns>True if settings are the same, otherwise false</returns>
		public override bool Equals(object obj)
		{
			bool isEquals = false;

			if (obj is SmtpEngine)
			{
				SmtpEngine otherSmtpEngine = (SmtpEngine)obj;

				if ((EmailAddress == otherSmtpEngine.EmailAddress) && (Protocol.Equals(otherSmtpEngine.Protocol)) && (ToFilter == otherSmtpEngine.ToFilter) && (DomainFilter == otherSmtpEngine.DomainFilter))
				{
					isEquals = true;
				}
			}

			return isEquals;
		}

		/// <summary>
		/// Creates a unique ID for this object
		/// </summary>
		/// <returns></returns>
		public override int GetHashCode()
		{
			return base.GetHashCode();
		}

		/// <summary>
		/// Validates these settings
		/// </summary>
		/// <returns>True if all settings are valid, otherwise false</returns>
		public override bool ValidateSettings()
		{
			bool isValid = true;

			isValid &= ((ToFilter != EmailToFilter.SpecificAddress) || (!string.IsNullOrEmpty(EmailAddress)));
			isValid &= ((ToFilter != EmailToFilter.FilterDomain) || (!string.IsNullOrEmpty(DomainFilter)));

			return (isValid && base.ValidateSettings());
		}

		/// <summary>
		/// Finds what email addresses a message needs to be sent to
		/// </summary>
		/// <param name="message">Message to send</param>
		/// <returns>A list of email addresses</returns>
		public List<string> GetToEmailAddresses(string message)
		{
			List<string> toAddresses = new List<string>();

			switch (ToFilter)
			{
				case EmailToFilter.SpecificAddress:
					toAddresses.Add(EmailAddress);
					break;
				case EmailToFilter.FilterDomain:
					toAddresses = getToHeaderFromEmail(message, DomainFilter);
					break;
				case EmailToFilter.AllToHeaders:
					toAddresses = getToHeaderFromEmail(message, "");
					break;
			}

			return toAddresses;
		}
		#endregion
		#region Private Methods
		private List<string> getToHeaderFromEmail(string message, string filter)
		{
			Regex toRegex = new Regex(@"^\W*(to|cc|bcc):[^:]*?(""[^""]*"")?[^:]*?([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]*)", RegexOptions.IgnoreCase | RegexOptions.Multiline);
			List<string> addresses = new List<string>();
			MatchCollection toMatch = toRegex.Matches(message);

			foreach (Match thisMatch in toMatch)
			{
				if (thisMatch.Groups[3].Value.EndsWith(filter))
				{
					addresses.Add(thisMatch.Groups[3].Value);
				}
			}

			return addresses;
		}
		#endregion
	}

	/// <summary>
	/// Different ways to find the to address to send email to
	/// </summary>
	public enum EmailToFilter
	{
		/// <summary>
		/// Send the email to a specific address
		/// </summary>
		SpecificAddress,
		/// <summary>
		/// Check the headers, but filter by specific domain
		/// </summary>
		FilterDomain,
		/// <summary>
		/// Check the headers, and send to all in the headers
		/// </summary>
		AllToHeaders
	}
}
